/***************************************************************************//**
 * @file CGI/Airport.cpp
 * @author  Marek M. Cel <marekcel@mscsim.org>
 *
 * @section LICENSE
 *
 * Copyright (C) 2014 Marek M. Cel
 *
 * This file is part of MScSim. You can redistribute and modify it under
 * the terms of GNU General Public License as published by the Free Software
 * Foundation; either version 3 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.
 * 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * Further information about the GNU General Public License can also be found
 * on the world wide web at http://www.gnu.org.
 ******************************************************************************/
#ifndef AIRPORT_CPP
#define AIRPORT_CPP
#endif

////////////////////////////////////////////////////////////////////////////////

#ifndef _USE_MATH_DEFINES
#   define _USE_MATH_DEFINES
#endif

////////////////////////////////////////////////////////////////////////////////

#include <cmath>
#include <iostream>

#include <osg/CoordinateSystemNode>
#include <osg/Geode>
#include <osg/Geometry>
#include <osg/Material>
#include <osg/PositionAttitudeTransform>
#include <osg/ShapeDrawable>
#include <osg/Texture2D>

#include <osgDB/ReadFile>

#include <CGI/Airport.h>

////////////////////////////////////////////////////////////////////////////////

using namespace std;

////////////////////////////////////////////////////////////////////////////////

const double Airport::m_elevation    =   0.3;
const double Airport::m_halfWidth    =  25.0;
const double Airport::m_taxiDistance = 150.0;
const double Airport::m_apronWidth   = 100.0;

////////////////////////////////////////////////////////////////////////////////

Airport::Airport( CGI_Data *data ) :
    m_data ( data ),

    m_apron     ( 0 ),
    m_buildings ( 0 ),
    m_lights    ( 0 ),
    m_runway    ( 0 ),
    m_taxiways  ( 0 ),
    m_terrain   ( 0 )
{
    m_airportGroup = createAirportGroup();
}

////////////////////////////////////////////////////////////////////////////////

Airport::~Airport()
{
    if ( m_apron     ) delete m_apron;     m_apron     = 0;
    if ( m_buildings ) delete m_buildings; m_buildings = 0;
    if ( m_lights    ) delete m_lights;    m_lights    = 0;
    if ( m_runway    ) delete m_runway;    m_runway    = 0;
    if ( m_taxiways  ) delete m_taxiways;  m_taxiways  = 0;
    if ( m_terrain   ) delete m_terrain;   m_terrain   = 0;
}

////////////////////////////////////////////////////////////////////////////////

void Airport::update()
{
//    m_apron->update();
//    m_buildings->update();
//    m_lights->update();
//    m_runway->update();
//    m_taxiways->update();
//    m_terrain->update();
}

////////////////////////////////////////////////////////////////////////////////

void Airport::createSegment( osg::Group* group, double ulx, double uly, double lrx, double lry,
                                 const std::string& textureFile, bool computeTextureSize )
{
    osg::ref_ptr<osg::Geode> segmentGeode = new osg::Geode();
    segmentGeode->setName( "SegmentGeode" );

    // geometry
    osg::ref_ptr<osg::Geometry> segmentGeometry = new osg::Geometry();
    segmentGeometry->setName( "SegmentGeometry" );

    osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
    osg::ref_ptr<osg::Vec2Array> texCoors = new osg::Vec2Array;

    vertices->push_back( osg::Vec3d( lrx, uly, 0.0 ) );
    vertices->push_back( osg::Vec3d( lrx, lry, 0.0 ) );
    vertices->push_back( osg::Vec3d( ulx, lry, 0.0 ) );
    vertices->push_back( osg::Vec3d( ulx, uly, 0.0 ) );

    if ( computeTextureSize )
    {
        double y = fabs( lrx - ulx ) / ( 2.0 * Airport::m_halfWidth );
        double x = fabs( uly - lry ) / ( 2.0 * Airport::m_halfWidth );

        texCoors->push_back( osg::Vec2( 0, 0 ) );
        texCoors->push_back( osg::Vec2( x, 0 ) );
        texCoors->push_back( osg::Vec2( x, y ) );
        texCoors->push_back( osg::Vec2( 0, y ) );
    }
    else
    {
        texCoors->push_back( osg::Vec2( 0, 0 ) );
        texCoors->push_back( osg::Vec2( 1, 0 ) );
        texCoors->push_back( osg::Vec2( 1, 1 ) );
        texCoors->push_back( osg::Vec2( 0, 1 ) );
    }

    segmentGeometry->setVertexArray( vertices.get() );

    osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array;
    normals->push_back( osg::Vec3d( 0.0, 0.0, -1.0 ) );
    segmentGeometry->setNormalArray( normals.get() );
    segmentGeometry->setNormalBinding( osg::Geometry::BIND_OVERALL );

    segmentGeometry->addPrimitiveSet( new osg::DrawArrays( osg::PrimitiveSet::QUADS, 0, vertices->size() ) );

    // texture
    osg::ref_ptr<osg::Image> segmentImage = osgDB::readImageFile( textureFile );

    if ( segmentImage.valid() )
    {
        osg::ref_ptr<osg::StateSet> segmentStateSet = segmentGeode->getOrCreateStateSet();

        segmentGeometry->setTexCoordArray( 0, texCoors.get() );

        osg::ref_ptr<osg::Texture2D> segmentTexture = new osg::Texture2D();
        segmentTexture->setImage( segmentImage.get() );

        segmentTexture->setWrap( osg::Texture2D::WRAP_S, osg::Texture2D::REPEAT );
        segmentTexture->setWrap( osg::Texture2D::WRAP_T, osg::Texture2D::REPEAT );

        segmentTexture->setMaxAnisotropy( 8.0f );

        segmentStateSet->setTextureAttributeAndModes( 0, segmentTexture.get(), osg::StateAttribute::ON );
    }
    else
    {
        cerr << "ERROR! Cannot open texture file: " << textureFile << endl;
    }

    segmentGeode->addDrawable( segmentGeometry.get() );

//    // material
//    {
//        osg::ref_ptr<osg::StateSet> segmentStateSet = segmentGeode->getOrCreateStateSet();
//        osg::ref_ptr<osg::Material> segmentMaterial = new osg::Material();
//        segmentMaterial->setDiffuse( osg::Material::FRONT, osg::Vec4( 1.0f, 1.0f, 1.0f, 1.0f ) );
//        segmentStateSet->setAttribute( segmentMaterial.get() );
//    }

    group->addChild( segmentGeode.get() );
}

////////////////////////////////////////////////////////////////////////////////

void Airport::createSegment( osg::Group* group, double x_from, double x_unto, double y_cntr,
                                 const std::string& textureFile )
{
    double sign_L = 0.0;
    double sign_R = 0.0;

    if ( x_unto > x_from )
    {
        sign_L = -1.0;
        sign_R =  1.0;
    }
    else
    {
        sign_L =  1.0;
        sign_R = -1.0;
    }

    double ulx = x_unto;
    double uly = y_cntr + sign_L * Airport::m_halfWidth;
    double lrx = x_from;
    double lry = y_cntr + sign_R * Airport::m_halfWidth;

    Airport::createSegment( group, ulx, uly, lrx, lry, textureFile );
}

////////////////////////////////////////////////////////////////////////////////

osg::ref_ptr<osg::Group> Airport::createAirportGroup()
{
    osg::ref_ptr<osg::Group> airportGroup = new osg::Group();
    airportGroup->setName( "AirportGroup" );

    osg::ref_ptr<osg::PositionAttitudeTransform> airportPAT = new osg::PositionAttitudeTransform();
    airportPAT->setName( "AirportPAT" );

    osg::ref_ptr<osg::EllipsoidModel> em = new osg::EllipsoidModel();

    // attitude
    osg::Matrixd T_wgs2enu;

    em->computeLocalToWorldTransformFromLatLongHeight( 0.0, 0.0, Airport::m_elevation, T_wgs2enu );

    osg::Quat q_wgs2enu = T_wgs2enu.getRotate();

    osg::Quat q_enu2ned;

    q_enu2ned.makeRotate( M_PI       , osg::Vec3d( 1.0, 0.0, 0.0 ),
                          0.0        , osg::Vec3d( 0.0, 1.0, 0.0 ),
                          M_PI / 2.0 , osg::Vec3d( 0.0, 0.0, 1.0 ) );

    double x = 0.0;
    double y = 0.0;
    double z = 0.0;

    em->convertLatLongHeightToXYZ( 0.0, 0.0, Airport::m_elevation, x, y, z );

    airportPAT->setPosition( osg::Vec3( x, y, z ) );
    airportPAT->setAttitude( q_enu2ned * q_wgs2enu );

    m_apron     = new AirportApron     ( m_data );
    m_buildings = new AirportBuildings ( m_data );
    m_lights    = new AirportLights    ( m_data );
    m_runway    = new AirportRunway    ( m_data );
    m_taxiways  = new AirportTaxiways  ( m_data );
    m_terrain   = new AirportTerrain   ( m_data );

    airportPAT->addChild( m_apron->getAirportApronGroup().get()         );
    airportPAT->addChild( m_buildings->getAirportBuildingsGroup().get() );
    airportPAT->addChild( m_lights->getAirportLightsGroup().get()       );
    airportPAT->addChild( m_runway->getAirportRunwayGroup().get()       );
    airportPAT->addChild( m_taxiways->getAirportTaxiwaysGroup().get()   );
    airportPAT->addChild( m_terrain->getAirportTerrainGroup().get()     );

    airportGroup->addChild( airportPAT.get() );

    return airportGroup;
}
